package com.happy3w.cmds.maven.model;

import com.happy3w.cmds.utils.XmlUtil;
import com.happy3w.java.ext.StringUtils;
import lombok.Getter;
import lombok.Setter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;

@Getter
@Setter
public class JavaModule {
    private String code;
    private String groupId;
    private String artifactId;
    private String packaging;
    private String fullPath;
    private Set<String> subModuleDirs = new HashSet<>();
    private List<JavaModule> subModules = new ArrayList<>();
    private Set<String> dependencies = new HashSet<>();
    private Set<String> outDependencies = new HashSet<>();
    private Set<String> managedDependencies = new HashSet<>();

    public Stream<JavaModule> streamSubModules() {
        return subModules == null ? Stream.empty() :subModules.stream();
    }

    public Stream<JavaModule> streamAllSubModules() {
        return Stream.concat(
                Stream.of(this),
                streamSubModules().flatMap(JavaModule::streamAllSubModules)
        );
    }

    public String takeKey() {
        return groupId + ":" + artifactId;
    }

    @Override
    public int hashCode() {
        return takeKey().hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof JavaModule j) {
            return takeKey().equals(j.takeKey());
        }
        return false;
    }

    private void loadFrom(Element root, String baseDir) {
        this.artifactId = XmlUtil.getText(root, "artifactId");
        this.groupId = XmlUtil.getText(root, "groupId");
        if (StringUtils.isEmpty(this.groupId)) {
            this.groupId = XmlUtil.getText(root, "parent/groupId");
        }
        this.packaging = XmlUtil.getText(root, "packaging");
        this.fullPath = baseDir;

        loadInfos(root);

        NodeList profiles = XmlUtil.getElements(root, "profiles/profile");
        XmlUtil.elements(profiles)
                .forEach(this::loadInfos);

        subModuleDirs.stream()
                .map(moduleDir -> JavaModule.extract(baseDir + "/" + moduleDir))
                .forEach(this.subModules::add);
    }

    private void loadInfos(Element element) {
        NodeList modules = XmlUtil.getElements(element, "modules/module");
        XmlUtil.elements(modules)
                .map(Element::getTextContent)
                .forEach(this.subModuleDirs::add);

        NodeList dependNodes = XmlUtil.getElements(element, "dependencies/dependency");
        XmlUtil.elements(dependNodes)
                .map(this::loadDependKey)
                .forEach(this.dependencies::add);

        NodeList managedDependNodes = XmlUtil.getElements(element, "dependencyManagement/dependencies/dependency");
        XmlUtil.elements(managedDependNodes)
                .map(this::loadDependKey)
                .forEach(this.managedDependencies::add);

    }

    private String loadDependKey(Element element) {
        String artifactId = XmlUtil.getText(element, "artifactId");
        String groupId = XmlUtil.getText(element, "groupId");
        return groupId + ":" + artifactId;
    }

    public static JavaModule extract(String projectDir) {
        String baseDir = projectDir.endsWith("/") ? projectDir.substring(0, projectDir.length() - 1) : projectDir;
        String pomFile = baseDir + "/pom.xml";

        // 通过xml工具打开pom.xml文件
        Document doc = XmlUtil.loadXml(pomFile);
        Element root = doc.getDocumentElement();

        JavaModule module = new JavaModule();
        module.loadFrom(root, baseDir);

        return module;
    }

    public void regularDepends(Map<String, JavaModule> moduleMap) {
        for (String d : this.dependencies) {
            if (!moduleMap.containsKey(d)) {
                this.outDependencies.add(d);
            }
        }
        this.dependencies.removeAll(this.outDependencies);
    }

    public Stream<ModuleFile> allFiles() {
        try {
            return Files.walk(Paths.get(this.fullPath))
                    .filter(Files::isRegularFile)
                    .map(path -> new ModuleFile(path, this));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
